﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;
using System.IO;
using System.Net;

namespace Client.p2p
{
    public class Message
    {
        private string content;
        public string Content
        {
            get { return content; }
        }
        public byte[] MetaData
        {
            get
            {
                return MetaDataBuilder.TimeAndContent(time, content, Encoding.ASCII);
            }
        }

        private string senderName;
        public string SenderName
        {
            get { return senderName; }
        }

        private string targetName;
        public string TargetName
        {
            get { return targetName; }
        }

        private DateTime time;
        public DateTime Time
        {
            get { return time; }
        }

        public Message(string senderName, string targetName, DateTime time, string content)
        {
            this.senderName = senderName;
            this.targetName = targetName;
            this.time = time;
            this.content = content;
        }

        public Message(string senderName, string targetName, byte[] bytes)
        {
            this.senderName = senderName;
            this.targetName = targetName;
            int lendate = BitConverter.ToInt32(bytes, 0);
            this.time = DateTime.Parse(Encoding.ASCII.GetString(bytes, 4, lendate));
            int len = BitConverter.ToInt32(bytes, 4 + lendate);
            this.content = Encoding.ASCII.GetString(bytes, 4 + lendate + 4, len);
        }

        public Message(string senderName, string targetName, byte[] bytes, DateTime time)
        {
            this.senderName = senderName;
            this.targetName = targetName;
            int lendate = BitConverter.ToInt32(bytes, 0);
            this.time = DateTime.Parse(Encoding.ASCII.GetString(bytes, 4, lendate));
            int len = BitConverter.ToInt32(bytes, 4 + lendate);
            this.content = Encoding.ASCII.GetString(bytes, 4 + lendate + 4, len);
            this.time = time;
        }
    }

    public class MessageProcessor
    {
        private GlobalCmdBackground globalCmdBg;
        private Queue<Message> SendMessageQueue = new Queue<Message>();
        private System.Threading.Semaphore queueSemaphore = new System.Threading.Semaphore(0, int.MaxValue);
        private BackgroundWorker bwMsgProc;
        private UserSession session;
        private UserData userdata;
        private Dictionary<string, IPEndPoint> userEndpoints = new Dictionary<string, IPEndPoint>();

        internal UserData UserData
        {
            get { return userdata; }
        }

        public MessageProcessor(GlobalCmdBackground globalCmdBg, UserData userdata, UserSession session)
        {
            this.globalCmdBg = globalCmdBg;
            this.userdata = userdata;
            this.session = session;
            this.globalCmdBg.CommandReceived += new CommandReceivedEventHandler(CommandReceiver);
            this.globalCmdBg.CommandSent += new CommandSentEventHandler(cmdBg_CommandSent);
            this.globalCmdBg.CommandFailed += new CommandSendingFailedEventHandler(cmdBg_CommandFailed);
            this.bwMsgProc = new BackgroundWorker();
            this.bwMsgProc.DoWork += new DoWorkEventHandler(MsgProc);
            this.bwMsgProc.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwMsgProc_RunWorkerCompleted);
            this.bwMsgProc.WorkerSupportsCancellation = true;
            this.bwMsgProc.RunWorkerAsync();
        }

        void bwMsgProc_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            ((BackgroundWorker)sender).Dispose();
            GC.Collect();
        }

        private void MsgProc(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker bw = sender as BackgroundWorker;
            while(!bw.CancellationPending)
            {
                queueSemaphore.WaitOne();
                if (SendMessageQueue.Count > 0)
                {
                    Message msg = SendMessageQueue.Dequeue();
                    IPEndPoint endpoint;
                    if (userEndpoints.TryGetValue(msg.TargetName, out endpoint))
                    {
                        this.globalCmdBg.SendCommand(new Command(CommandType.Message,
                            endpoint,
                            msg.MetaData));
                    }
                    else
                    {
                        System.Diagnostics.Debug.Print("Name没有对应的Endpoint！ in MsgProc");
                        OnMessageSendingFailed(new MessageEventArgs(msg));
                    }
                }
                else
                {
                    System.Diagnostics.Debug.Print("MsgProc有问题\n");
                }
            }
        }

        private void CommandReceiver(object sender, CommandEventArgs e)
        {
            Command cmd = e.Command;
            try
            {
	            switch (cmd.CommandType)
	            {
	                case CommandType.QueryLatestUserData:
	                    DateTime dt = DateTime.Parse(Encoding.ASCII.GetString(cmd.MetaData));
	                    if (dt < File.GetLastWriteTime(global::Client.Properties.Resources.UserDataFileName))
	                    {
	                        this.globalCmdBg.SendCommand(new Command(CommandType.QueryLatestUserDataAck,
	                            cmd.SenderEP,
	                            MetaDataBuilder.TimeAndFile(File.GetLastWriteTime(Properties.Resources.UserDataFileName),
	                                Properties.Resources.UserDataFileName)));
	                    }
	                    break;
	                case CommandType.UserLogoffInform:
                        IPEndPoint ep;
                        if (userEndpoints.TryGetValue(cmd.SenderName, out ep))
                        {
                            userEndpoints.Remove(cmd.SenderName);
                            OnUserOffline(new UserEventArgs(cmd.SenderName));
                        }
                        break;
	                case CommandType.UserLogonInform:
                        if (cmd.SenderName != this.session.Name)
                        {
	                        userEndpoints[cmd.SenderName] = cmd.SenderEP;
	                        this.globalCmdBg.SendCommand(new Command(CommandType.UserOnlineAck,
	                            cmd.SenderEP,
                                BitConverter.GetBytes(this.globalCmdBg.Port)));
	                        OnUserOnline(new UserEventArgs(cmd.SenderName));
                        }
                        break;
                    case CommandType.UserOnlineAck:
                        userEndpoints[cmd.SenderName] = new IPEndPoint(cmd.SenderEP.Address, BitConverter.ToInt32(cmd.MetaData, 0));
                        OnUserOnline(new UserEventArgs(cmd.SenderName));
                        break;
	                case CommandType.UserStillOnline:
	                    break;
	                case CommandType.AskIfOnline:
                        if (cmd.SenderName == session.Name)
                            this.globalCmdBg.SendCommand(new Command(CommandType.UserHasAlreadyOnline,
                                cmd.SenderEP));
	                    else if (Encoding.Unicode.GetString(cmd.MetaData) == session.Name)
	                        this.globalCmdBg.SendCommand(new Command(CommandType.UserOnlineAck,
	                            cmd.SenderEP));
	                    break;
                    case CommandType.FileTransferApply:
                        OnFileReceiving(new FileReceivingEventArgs(cmd.SenderName,
                            MetaDataParser.FileAttribute(cmd.MetaData)));
                        break;
                    case CommandType.FileTransferAck:
                        int[] ret = MetaDataParser.FileTransferNoAndPort(cmd.MetaData);
                        OnFileConfirmed(new FileConfirmedEventArgs(cmd.SenderName,
                            new IPEndPoint(cmd.SenderEP.Address, ret[1]),
                            ret[0]));
                        break;
                    case CommandType.FileTransferDeny:
                        OnFileDenied(new FileTransferEventArgs(cmd.SenderName, BitConverter.ToInt32(cmd.MetaData, 0)));
                        break;
                    case CommandType.FileTransferCancel:
                        object[] obj = MetaDataParser.FileTransferNoAndSource(cmd.MetaData);
                        OnFileCanceled(new FileCanceledEventArgs(cmd.SenderName, (int)obj[0], (string)obj[1]));
                        break;
	                case CommandType.Message:
                        OnMessageReceived(new MessageEventArgs(new Message(cmd.SenderName, this.session.Name, cmd.MetaData)));
	                    break;
	            }
            }
            catch (System.Exception ex)
            {
                System.Diagnostics.Debug.Print(ex.Message);
            }
        }

        private void cmdBg_CommandReceived(object sender, CommandEventArgs e)
        {
            Command cmd = e.Command;
            switch (cmd.CommandType)
            {
                case CommandType.Message:
                    OnMessageReceived(new MessageEventArgs(new Message(cmd.SenderName, this.session.Name, cmd.MetaData)));
                    break;
            }
        }

        private void cmdBg_CommandSent(object sender, CommandEventArgs e)
        {
            Command cmd = e.Command;
            switch (cmd.CommandType)
            {
                case CommandType.Message:
                    foreach (KeyValuePair<string, IPEndPoint> ep in this.userEndpoints)
                    {
                        if (ep.Value == cmd.TargetEP)
                        {
                            OnMessageSent(new MessageEventArgs(new Message(cmd.SenderName,
                                ep.Key,
                                cmd.MetaData)));
                            break;
                        }
                    }
                    break;
            }
        }

        private void cmdBg_CommandFailed(object sender, CommandEventArgs e)
        {
            Command cmd = e.Command;
            switch (cmd.CommandType)
            {
                case CommandType.Message:
                    foreach (KeyValuePair<string, IPEndPoint> ep in this.userEndpoints)
                    {
                        if (ep.Value == cmd.TargetEP)
                        {
                            OnMessageSendingFailed(new MessageEventArgs(new Message(cmd.SenderName,
                                ep.Key,
                                cmd.MetaData,
                                DateTime.Now)));
                            break;
                        }
                    }
                    break;
            }
        }

        #region Public Methods

        public void SendMessage(Message msg)
        {
            SendMessageQueue.Enqueue(msg);
            queueSemaphore.Release();
        }

        public void SendLogonInform()
        {
            this.globalCmdBg.SendCommand(new Command(CommandType.UserLogonInform,
                this.globalCmdBg.MulticastEP,
                (byte[])null));
        }

        public void SendLogoffInform()
        {
            this.globalCmdBg.SendCommand(new Command(CommandType.UserLogoffInform,
                this.globalCmdBg.MulticastEP,
                (byte[])null));
        }

        public void SendFileTransferApply(string TargetName, FileAttr fileAttr)
        {
            IPEndPoint endpoint;
            if (userEndpoints.TryGetValue(TargetName, out endpoint))
            {
                this.globalCmdBg.SendCommand(new Command(CommandType.FileTransferApply,
                    endpoint,
                    MetaDataBuilder.FileAttribute(fileAttr)));
            }
            else
            {
                System.Diagnostics.Debug.Print("Name没有对应的Endpoint！ in SendFileTransferApply");
            }
        }

        public void SendFileTransferAck(string TargetName, int No, int Port)
        {
            IPEndPoint endpoint;
            if (userEndpoints.TryGetValue(TargetName, out endpoint))
            {
                this.globalCmdBg.SendCommand(new Command(CommandType.FileTransferAck,
                    endpoint,
                    MetaDataBuilder.FileTransferNoAndPort(No, Port)));
            }
            else
            {
                System.Diagnostics.Debug.Print("Name没有对应的Endpoint！ in SendFileTransferAck");
            }
        }

        public void SendFileTransferDeny(string TargetName, int No)
        {
            IPEndPoint endpoint;
            if (userEndpoints.TryGetValue(TargetName, out endpoint))
            {
                this.globalCmdBg.SendCommand(new Command(CommandType.FileTransferDeny,
                    endpoint,
                    BitConverter.GetBytes(No)));
            }
            else
            {
                System.Diagnostics.Debug.Print("Name没有对应的Endpoint！ in SendFileTransferDeny");
            }
        }

        public void SendFileTransferCancel(string TargetName, int No, string SourceName)
        {
            IPEndPoint endpoint;
            if (userEndpoints.TryGetValue(TargetName, out endpoint))
            {
                this.globalCmdBg.SendCommand(new Command(CommandType.FileTransferCancel,
                    endpoint,
                    MetaDataBuilder.FileTransferNoAndSource(No, SourceName)));
            }
            else
            {
                System.Diagnostics.Debug.Print("Name没有对应的Endpoint！ in SendFileTransferCancel");
            }
        }

        #endregion

        #region Events

        public event MessageReceivedEventHandler MessageReceived;
        protected virtual void OnMessageReceived(MessageEventArgs e)
        {
            if (MessageReceived != null)
            {
                Control target = MessageReceived.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(MessageReceived, new object[] { this, e });
                else
                    MessageReceived(this, e);
            }
        }

        public event MessageSentEventHandler MessageSent;
        protected virtual void OnMessageSent(MessageEventArgs e)
        {
            if (MessageSent != null)
            {
                Control target = MessageSent.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(MessageSent, new object[] { this, e });
                else
                    MessageSent(this, e);
            }
        }

        public event MessageSendingFailedEventHandler MessageSendingFailed;
        protected virtual void OnMessageSendingFailed(MessageEventArgs e)
        {
            if (MessageSendingFailed != null)
            {
                Control target = MessageSendingFailed.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(MessageSendingFailed, new object[] { this, e });
                else
                    MessageSendingFailed(this, e);
            }
        }

        public event UserOnlineEventHandler UserOnline;
        protected virtual void OnUserOnline(UserEventArgs e)
        {
            if (UserOnline != null)
            {
                Control target = UserOnline.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(UserOnline, new object[] { this, e });
                else
                    UserOnline(this, e);
            }
        }

        public event UserOfflineEventHandler UserOffline;
        protected virtual void OnUserOffline(UserEventArgs e)
        {
            if (UserOffline != null)
            {
                Control target = UserOffline.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(UserOffline, new object[] { this, e });
                else
                    UserOffline(this, e);
            }
        }

        public event FileReceivingEventHandler FileReceiving;
        protected virtual void OnFileReceiving(FileReceivingEventArgs e)
        {
            if (FileReceiving != null)
            {
                Control target = FileReceiving.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(FileReceiving, new object[] { this, e });
                else
                    FileReceiving(this, e);
            }
        }

        public event FileConfirmedEventHandler FileConfirmed;
        protected virtual void OnFileConfirmed(FileConfirmedEventArgs e)
        {
            if (FileConfirmed != null)
            {
                Control target = FileConfirmed.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(FileConfirmed, new object[] { this, e });
                else
                    FileConfirmed(this, e);
            }
        }

        public event FileDeniedEventHandler FileDenied;
        protected virtual void OnFileDenied(FileTransferEventArgs e)
        {
            if (FileDenied != null)
            {
                Control target = FileDenied.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(FileDenied, new object[] { this, e });
                else
                    FileDenied(this, e);
            }
        }

        public event FileCanceledEventHandler FileCanceled;
        protected virtual void OnFileCanceled(FileCanceledEventArgs e)
        {
            if (FileCanceled != null)
            {
                Control target = FileCanceled.Target as Control;
                if (target != null && target.InvokeRequired)
                    target.Invoke(FileCanceled, new object[] { this, e });
                else
                    FileCanceled(this, e);
            }
        }

        #endregion
    }
}
